﻿/*
 * Released under Microsoft Public License (Ms-PL) 
 * Sponsored by Development Platform Evangelism unit of Microsoft Israel
 * 
 * Copyright © 2008 by Tamir Khason
 * http://blogs.microsoft.co.il/blogs/tamir/
 * http://sharpsoft.net/
 * 
 * More information including licensing and term of use
 * can be found on http://www.codeplex.com/SilverlightRTL/
 * 
 */

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Browser;
using System.Text;

namespace System.Windows.BidiControls
{
    /// <summary>
    /// Represens a control that can be used for text display
    /// </summary>
    [ContentProperty("Inlines")]
    [TemplatePart(Name = TextBlock.TextElementName, Type=typeof(System.Windows.Controls.TextBlock))]
    public class TextBlock : Control
    {        
        #region props
        #region Inlines
        List<Inline> inlines;
        /// <summary> 
        /// Gets or sets the Inlines possible Value of the InlineCollection object.
        /// </summary> 
        public InlineCollection Inlines
        {
            get { return (InlineCollection)GetValue(InlinesProperty); }
            set { SetValue(InlinesProperty, value); }
        }

        /// <summary> 
        /// Identifies the Inlines dependency property.
        /// </summary> 
        public static readonly DependencyProperty InlinesProperty =
                    DependencyProperty.Register(
                          "Inlines",
                          typeof(InlineCollection),
                          typeof(TextBlock),
                          new PropertyMetadata(OnInlinesPropertyChanged));

        /// <summary>
        /// InlinesProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBlock that changed its Inlines.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnInlinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBlock _TextBlock = d as TextBlock;
            if (_TextBlock != null)
            {
                _TextBlock.inlines = new List<Inline>();
                 InlineCollection ic = (InlineCollection)e.NewValue;
                 for (int i = 0; i < ic.Count; i++)
                {
                    Run r = ic[i] as Run;
                    if (r != null)
                    {
                        Run nr = r.Clone();
                        if(_TextBlock.FlowDirection == FlowDirection.RightToLeft)
                            nr.Text = NBidi.NBidi.LogicalToVisual(r.Text);

                        _TextBlock.inlines.Add(nr);
                    }
                    else
                    {
                        _TextBlock.inlines.Add(ic[i].Clone());
                    }
                }
                
            }
            
        }
        #endregion Inlines

        #region LineHeight

        /// <summary> 
        /// Gets or sets the LineHeight possible Value of the double object.
        /// </summary> 
        [TypeConverter(typeof(LengthConverter))]
        public double LineHeight
        {
            get { return (double)GetValue(LineHeightProperty); }
            set { SetValue(LineHeightProperty, value); }
        }

        /// <summary> 
        /// Identifies the LineHeight dependency property.
        /// </summary> 
        public static readonly DependencyProperty LineHeightProperty =
                    DependencyProperty.Register(
                          "LineHeight",
                          typeof(double),
                          typeof(TextBlock),
                          null);

        #endregion LineHeight

        #region LineStackingStrategy

        /// <summary> 
        /// Gets or sets the LineStackingStrategy possible Value of the LineStackingStrategy object.
        /// </summary> 
        [TypeConverter(typeof(EnumValueConverter<LineStackingStrategy>))]
        public LineStackingStrategy LineStackingStrategy
        {
            get { return (LineStackingStrategy)GetValue(LineStackingStrategyProperty); }
            set { SetValue(LineStackingStrategyProperty, value); }
        }

        /// <summary> 
        /// Identifies the LineStackingStrategy dependency property.
        /// </summary> 
        public static readonly DependencyProperty LineStackingStrategyProperty =
                    DependencyProperty.Register(
                          "LineStackingStrategy",
                          typeof(LineStackingStrategy),
                          typeof(TextBlock),
                          null);
        #endregion LineStackingStrategy

        #region TextAlignment

        /// <summary> 
        /// Gets or sets the TextAlignment possible Value of the TextAlignment object.
        /// </summary> 
        [TypeConverter(typeof(EnumValueConverter<TextAlignment>))]
        [DefaultValue(TextAlignment.Right)]
        [Category("Text"), Description("Specifies the alignment of text.")]
        public TextAlignment TextAlignment
        {
            get { return (TextAlignment)GetValue(TextAlignmentProperty); }
            set { SetValue(TextAlignmentProperty, value); }
        }

        /// <summary> 
        /// Identifies the TextAlignment dependency property.
        /// </summary> 
        public static readonly DependencyProperty TextAlignmentProperty =
                    DependencyProperty.Register(
                          "TextAlignment",
                          typeof(TextAlignment),
                          typeof(TextBlock),
                          new PropertyMetadata(OnTextAlignmentPropertyChanged));
        //PATCH: workaround for DefaultValueAttribute bug
        bool tapWasSet = false;
        private static void OnTextAlignmentPropertyChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
        {
            TextBlock _tb = s as TextBlock;
            if (_tb != null)
            {
                //PATCH: workaround for DefaultValueAttribute bug
                _tb.tapWasSet = true;
            }
        }

        #endregion TextAlignment

        #region TextDecorations

        /// <summary> 
        /// Gets or sets the TextDecorations possible Value of the TextDecorationCollection object.
        /// </summary> 
        [Category("Text"), Description("Specifies the decorations of text.")]
        public TextDecorationCollection TextDecorations
        {
            get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
            set { SetValue(TextDecorationsProperty, value); }
        }

        /// <summary> 
        /// Identifies the TextDecorations dependency property.
        /// </summary> 
        public static readonly DependencyProperty TextDecorationsProperty =
                    DependencyProperty.Register(
                          "TextDecorations",
                          typeof(TextDecorationCollection),
                          typeof(TextBlock),
                          null);

        
        #endregion TextDecorations

        #region Text
        internal string LocalText
        {
            get { return (string)GetValue(LocalTextProperty); }
            set { SetValue(LocalTextProperty, value); }
        }
        static readonly DependencyProperty LocalTextProperty =
                    DependencyProperty.Register(
                          "LocalText",
                          typeof(string),
                          typeof(TextBlock),
                          null);
        /// <summary> 
        /// Gets or sets the Text possible Value of the string object.
        /// </summary> 
        [Category("Common Properties"), Description("Get or Set the text contents of this element")]
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        /// <summary> 
        /// Identifies the Text dependency property.
        /// </summary> 
        public static readonly DependencyProperty TextProperty =
                    DependencyProperty.Register(
                          "Text",
                          typeof(string),
                          typeof(TextBlock),
                          new PropertyMetadata(OnTextPropertyChanged));

        /// <summary>
        /// TextProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBlock that changed its Text.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBlock _TextBlock = d as TextBlock;
            if (_TextBlock != null)
            {
                _TextBlock.resetTextData((string)e.NewValue);
            }
        }
        private void resetTextData(string data)
        {
            if (this.FlowDirection == FlowDirection.RightToLeft)
            {
                //add stack strategy (after layout)
                Dispatcher.BeginInvoke(delegate
                    {
                        if (!string.IsNullOrEmpty(data) && this.TextWrapping == TextWrapping.Wrap && this.ActualWidth > 0)
                        {
                            //cannot get glyphs. Use fallback rounded chars
                            char nl = (char)10;
                            double pxC = FontSize * .72;

                            StringBuilder sb = new StringBuilder(data);
                            int idx = 0;
                            int pidx = 0;
                            for (int i = 0; i < sb.Length; i++)
                            {
                                NBidi.BidiCharacterType ct = NBidi.UnicodeCharacterDataResolver.GetBidiCharacterType(sb[i]);
                                if (ct == NBidi.BidiCharacterType.NSM) continue;
                                if (ct == NBidi.BidiCharacterType.WS || ct == NBidi.BidiCharacterType.B || i == sb.Length - 1)
                                {
                                    //can wrap
                                    if (NBidi.UnicodeCharacterDataResolver.GetBidiCharacterType(sb[pidx]) == NBidi.BidiCharacterType.B |
                                        idx * pxC >= this.ActualWidth)
                                    {
                                        sb[pidx] = nl;
                                        idx = i - pidx;
                                    }
                                    else
                                    {
                                        string sl = NBidi.NBidi.LogicalToVisual(data.Substring(pidx, i - pidx));
                                        idx += sl.CountVisibleLetter();
                                    }

                                    pidx = i;
                                }
                            }
                            data = sb.ToString();
                        }
                        this.LocalText = NBidi.NBidi.LogicalToVisual(NBidi.BidiChars.RLM + data);
                    });
            }
            else
            {
                this.LocalText = data;
            }
        }
        #endregion Text

        #region AllowAlignmentOnFlowChange

        /// <summary> 
        /// Allows TextAlignment change when TextFlow property changed
        /// Gets or sets the AllowAlignmentOnFlowChange possible Value of the bool object.
        /// </summary> 
        [DefaultValue(true)]
        [Category("Layout"), Description("Allows the control to change it alignment based on flow direction")]
        public bool AllowAlignmentOnFlowChange
        {
            get { return (bool)GetValue(AllowAlignmentOnFlowChangeProperty); }
            set { SetValue(AllowAlignmentOnFlowChangeProperty, value); }
        }

        /// <summary> 
        /// Identifies the AllowAlignmentOnFlowChange dependency property.
        /// </summary> 
        public static readonly DependencyProperty AllowAlignmentOnFlowChangeProperty =
                    DependencyProperty.Register(
                          "AllowAlignmentOnFlowChange",
                          typeof(bool),
                          typeof(TextBlock),
                          new PropertyMetadata(OnAllowAlignmentOnFlowChangeChanged));

        /// <summary>
        /// AllowAlignmentOnFlowChange property changed handler. 
        /// </summary>
        /// <param name="d">TextBlock that changed its Text.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnAllowAlignmentOnFlowChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBlock _TextBlock = d as TextBlock;
            if (_TextBlock != null)
            {
                _TextBlock.setCheckAlignment();
            }
        }

        void setCheckAlignment()
        {
            if (this.AllowAlignmentOnFlowChange)
            {
                this.TextAlignment = this.TextAlignment == TextAlignment.Left ? TextAlignment.Right : TextAlignment.Left;
            }
        }

        #endregion AllowAlignmentOnFlowChange

        #region TextWrapping

        /// <summary> 
        /// Gets or sets the TextWrapping possible Value of the TextWrapping object.
        /// </summary> 
        [TypeConverter(typeof(EnumValueConverter<TextWrapping>))]
        [Category("Text"), Description("Gets or sets a value that indicates how the TextBlock should wrap text")]
        [DefaultValue(TextWrapping.Wrap)]
        public TextWrapping TextWrapping
        {
            get { return (TextWrapping)GetValue(TextWrappingProperty); }
            set { SetValue(TextWrappingProperty, value); }
        }

        /// <summary> 
        /// Identifies the TextWrapping dependency property.
        /// </summary> 
        public static readonly DependencyProperty TextWrappingProperty =
                    DependencyProperty.Register(
                          "TextWrapping",
                          typeof(TextWrapping),
                          typeof(TextBlock),
                          new PropertyMetadata(OnTextWrappingPropertyChanged));
        //PATCH: workaround for DefaultValueAttribute bug
        bool wrapWasSet = false;
        private static void OnTextWrappingPropertyChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
        {
            TextBlock _tb = s as TextBlock;
            if (_tb != null)
            {
                //PATCH: workaround for DefaultValueAttribute bug
                _tb.wrapWasSet = true;
            }
        }
        
        #endregion TextWrapping
        #endregion

        #region overrides
        protected override void OnFlowDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            base.OnFlowDirectionPropertyChanged(d, e);
            resetTextData(this.Text);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            resetTextData(Text);
            return base.ArrangeOverride(finalSize);
        }
        #endregion

        #region ctor
        public TextBlock()
        {
            DefaultStyleKey = typeof(System.Windows.BidiControls.TextBlock);
        }
        
        public override void OnApplyTemplate()
        {            
            base.OnApplyTemplate();
            TextElement = GetTemplateChild(TextElementName) as System.Windows.Controls.TextBlock;
            initDefaults();

            if (inlines != null)
            {
                TextElement.Inlines.Clear();
                for (int i = 0; i < inlines.Count; i++)
                {
                    TextElement.Inlines.Add(inlines[i]);
                }
            }

            resetTextData(Text);
        }

        void initDefaults()
        {
            
            //PATCH: workaround for DefaultValueAttribute bug
            if (!tapWasSet)
            {

                if (!HtmlPage.IsEnabled || String.IsNullOrEmpty(HtmlPage.Document.Body.GetAttribute("align")))
                {
                    //default
                    TextAlignment = base.FlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right;
                }
                else
                {
                    string align = HtmlPage.Document.Body.GetAttribute("align");
                    TextAlignment = align.ToLower() == "right" ? TextAlignment.Right : TextAlignment.Left;
                }
            }
            if (!wrapWasSet)
            {
                TextWrapping = TextWrapping.Wrap;
            }

            setCheckAlignment();
        }

        #endregion

        #region Template Parts
        internal System.Windows.Controls.TextBlock TextElement { get; set; }
        internal const string TextElementName = "TextElement";
        #endregion
    }
}
